package com.lw.lwte.testengin.listener;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.lw.lwte.common.helper.FileHelper;
import com.lw.lwte.common.util.JsonUtils;
import com.lw.lwte.testengin.entity.ReportInfo;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.testng.*;
import org.testng.xml.XmlSuite;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;

/**
 * generate report from template
 *
 * @author liw
 **/
@Slf4j
public class CustomizeReportListener implements IReporter {
    public static final String OUTPUT_PATH = String.format("%s/target/lwte-result/report", System.getProperty("user.dir"));
    public static final String OUTPUT_REPORT = String.format("%s/lw-detail-report.html", OUTPUT_PATH);
    public static final String JSON_PATH = String.format("%s/lw-detail-report.json", OUTPUT_PATH);

    private int testsPass = 0;  // 测试通过的用例数

    private int testsFail = 0; // 失败的用例数

    private int testsSkip = 0;  // 跳过的案例数

    private String beginTime;   // 开始执行的时间

    private long totalTime; // 总耗时

    private String name;    // 报告名称

    /**
     * 构造方法，使用当前时间戳作为报告名称。
     *
     * <p>
     * 使用 {@link SimpleDateFormat} 格式化当前时间戳，生成报告名称。
     * </p>
     */
    public CustomizeReportListener() {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssSSS");
        this.name = formatter.format(System.currentTimeMillis());
    }

    /**
     * 构造方法
     *
     * @param name 报告名称，如果传入null，则使用当前时间戳作为报告名称
     */
    public CustomizeReportListener(String name) {
        this();
        if (name != null) {
            this.name = name;
        }
    }

    /**
     * 生成测试报告
     *
     * @param xmlSuites 测试套件集合
     * @param suites    测试结果集合
     * @param outputDirectory 输出目录
     *
     * 该方法用于生成测试报告。首先，它会遍历所有的测试结果集合，统计通过、失败和跳过的测试数量，并将这些测试结果添加到列表中。
     */
    @Override
    public void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {
        List<ITestResult> list = new ArrayList<>();
        // 遍历测试结果集合
        for (ISuite suite : suites) {
            // 获取每个套件的测试结果映射
            Map<String, ISuiteResult> suiteResults = suite.getResults();
            // 遍历每个套件的结果
            for (ISuiteResult suiteResult : suiteResults.values()) {
                // 获取测试上下文
                ITestContext testContext = suiteResult.getTestContext();
                // 获取通过的测试结果集合
                IResultMap passedTests = testContext.getPassedTests();
                // 统计通过的测试数量
                testsPass = testsPass + passedTests.size();
                // 获取失败的测试结果集合
                IResultMap failedTests = testContext.getFailedTests();
                // 统计失败的测试数量
                testsFail = testsFail + failedTests.size();
                // 获取跳过的测试结果集合
                IResultMap skippedTests = testContext.getSkippedTests();
                // 统计跳过的测试数量
                testsSkip = testsSkip + skippedTests.size();
                // 将各类测试结果添加到列表中
                list.addAll(this.listTestResult(passedTests));
                list.addAll(this.listTestResult(failedTests));
                list.addAll(this.listTestResult(skippedTests));
                // 统计总耗时
                //統計total time
                totalTime += testContext.getEndDate().getTime() - testContext.getStartDate().getTime();
            }
        }
        // 对测试结果列表进行排序
        this.sort(list);
        // 输出测试结果
        this.outputResult(list);
        // 复制预设的脚本文件到输出目录中
        this.copyScriptFiles();

        // 以下部分被注释，表示生成邮件模板文件和发送邮件的功能暂时未启用
        //生成邮件模板文件
//        SimpleReportGenerator.generateEmailReport(suites);

        //发送邮件检查
//        if(!SendReportEmail.enableEmail()){
//            return;
//        }

        //发送邮件 todo: 接飞书推送
        try {
//            String emailName = String.format("Loongeasy测试报告-%s", DateTimeHelper.format(DateTimeFormat.DATE_TIME_0));
//            String content = FileHelper.readFileToString(new File(SimpleReportGenerator.OUTPUT_REPORT));

//            SendReportEmail.email(emailName, content, new String[]{CustomizeReportListener.OUTPUT_REPORT});
        } catch (Exception e) {
            log.error("Email sent failed.", e);
        }
    }


    /**
     * 从测试结果映射中获取所有测试结果，并转换为ArrayList返回
     *
     * @param resultMap 测试结果映射对象
     * @return 包含所有测试结果的ArrayList
     */
    private ArrayList<ITestResult> listTestResult(IResultMap resultMap) {
        Set<ITestResult> results = resultMap.getAllResults();
        return new ArrayList<>(results);
    }


    /**
     * 对测试结果列表进行排序
     *
     * @param list 待排序的测试结果列表
     *
     * 该方法通过Comparator对输入的测试结果列表进行排序。
     * 首先，根据测试结果的开始时间（以毫秒为单位）进行升序排序。
     * 如果存在开始时间相同的测试结果，则进一步根据测试结果的名称进行字典序排序。
     */
    private void sort(List<ITestResult> list) {
        list.sort(Comparator.comparingLong(ITestResult::getStartMillis)
                .thenComparing(ITestResult::getName));
    }

    /**
     * 输出测试结果
     *
     * @param list 测试结果列表
     * @throws IOException 如果在文件操作中发生I/O异常
     * 该方法用于将测试结果输出到HTML文件中。首先，它会遍历传入的测试结果列表，对于每个测试结果，
     * 会提取相关信息（如测试用例名称、耗时、状态、日志等），并将这些信息封装成ReportInfo对象。
     *
     */
    @SuppressWarnings("rawtypes")
    private void outputResult(List<ITestResult> list) {
        try {
            // 初始化存储测试结果信息的列表
            List<ReportInfo> listInfo = new ArrayList();
            // 初始化索引，用于判断是否为第一个测试结果
            int index = 0;
            // 遍历测试结果列表
            for (ITestResult result : list) {
                // 获取测试用例名称
                String tn = result.getTestContext().getCurrentXmlTest().getParameter("testCase");
                // 如果是第一个测试结果，则获取测试开始时间
                if (index == 0) {
                    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
                    beginTime = formatter.format(new Date(result.getStartMillis()));
                    index++;
                }
                // 计算测试耗时
                long spendTime = result.getEndMillis() - result.getStartMillis();
                // 转换测试状态为字符串
                String status = convertTestStatus(result.getStatus());
                // 获取测试结果日志
                List<String> log = Reporter.getOutput(result);
                // 将日志转换为HTML格式
                for (int i = 0; i < log.size(); i++) {
                    log.set(i, this.toHtml(log.get(i)));
                }
                // 获取测试结果中的异常信息
                Throwable throwable = result.getThrowable();
                // 如果存在异常信息，则添加到日志中
                if (throwable != null) {
                    log.add(this.toHtml(throwable.toString()));
                    StackTraceElement[] st = throwable.getStackTrace();
                    for (StackTraceElement stackTraceElement : st) {
                        log.add(this.toHtml("    " + stackTraceElement));
                    }
                }
                // 创建ReportInfo对象并设置相关信息
                String desc = result.getMethod().getDescription();
                ReportInfo info = ReportInfo.builder()
                        .name(tn)
                        .spendTime(spendTime + "ms")
                        .status(status)
                        .className(result.getInstanceName())
                        .methodName(result.getName())
                        .description(desc == null ? "" : desc)
                        .log(log)
                        .build();
                listInfo.add(info);
            }

            // 初始化存储统计结果的Map
            Map<String, Object> result = new HashMap<>();
            // 设置统计结果
            result.put("testName", name);
            result.put("testPass", testsPass);
            result.put("testFail", testsFail);
            result.put("testSkip", testsSkip);
            result.put("testAll", testsPass + testsFail + testsSkip);
            result.put("beginTime", beginTime);
            result.put("totalTime", totalTime + "ms");
            result.put("testResult", listInfo);

            // 将统计结果写入JSON文件
            JsonUtils.writeJsonToFile(result, JSON_PATH);

            // 创建Gson对象，用于将对象序列化为JSON字符串
            Gson gson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();

            // 读取HTML模板
            //read template
            InputStream stream = CustomizeReportListener.class.getResourceAsStream("/templates/report-template.html");
            String template = FileHelper.readStreamToString(stream);

            // 创建输出目录
            FileHelper.createDirectory(OUTPUT_PATH);

            // 写入HTML文件
            BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(OUTPUT_REPORT)), StandardCharsets.UTF_8));
            // 将统计结果替换到HTML模板中
            template = template.replaceFirst("\\$\\{resultData\\}", Matcher.quoteReplacement(gson.toJson(result)));
            output.write(template);
            output.flush();
            output.close();
        } catch (IOException e) {
            // 打印异常堆栈跟踪信息
            e.printStackTrace();
        }
    }


    /**
     * 复制脚本文件
     *
     * 该方法用于将预设的CSS和JS文件复制到指定的输出路径中。
     * 首先，将预设的CSS和JS文件名添加到列表中。
     * 然后，创建一个输出目录（如果尚不存在）。
     * 接下来，遍历列表中的每个文件名，从类路径中的模板文件夹中读取对应的文件内容，
     * 并将这些内容写入到输出目录中对应的新文件中。
     * 如果在复制过程中出现IO异常，将捕获该异常并打印堆栈跟踪信息。
     */
    private void copyScriptFiles() {
        List<String> scriptList = new ArrayList<>();
        //css
        scriptList.add("bootstrap.min.css");
        scriptList.add("bootstrap.min.css");
        scriptList.add("font-awesome.min.css");
        scriptList.add("animate.min.css");
        scriptList.add("chosen.css");
        //js
        scriptList.add("jquery.min.js");
        scriptList.add("bootstrap.min.js");
        scriptList.add("echarts.min.js");
        scriptList.add("chosen.jquery.js");

        String scriptPath = OUTPUT_PATH + "/css";
        FileHelper.createDirectory(scriptPath);
        try {
            for (String script : scriptList) {
                String filePath = scriptPath + "/" + script;
                //read template
                InputStream stream = CustomizeReportListener.class.getResourceAsStream("/templates/css/" + script);
                String template = FileHelper.readStreamToString(stream);

                BufferedWriter output = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath)), StandardCharsets.UTF_8));
                output.write(template);
                output.flush();
                output.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将字符串转换为HTML格式。
     *
     * @param str 待转换的字符串
     * @return 转换后的HTML字符串
     * @note 将字符串中的"<"、">"、空格、"\n"分别转换为HTML实体"&lt;"、"&gt;"、"&nbsp;"、"<br>"，
     *       注释掉的代码行是将字符串中的双引号转换为转义字符"\\\""，若需要处理双引号，请取消注释。
     */
    private String toHtml(String str) {
        if (str == null) {
            return "";
        } else {
            str = str.replaceAll("<", "&lt;");
            str = str.replaceAll(">", "&gt;");
            str = str.replaceAll(" ", "&nbsp;");
            str = str.replaceAll("\n", "<br>");
            //str = str.replaceAll("\"", "\\\\\"");
        }

        return str;
    }

    /**
     * 根据测试状态码，返回相应的状态信息字符串。
     *
     * @param status 测试状态码，整型值。
     * @return 返回对应的测试状态信息字符串。
     *                - 如果状态码为1，返回"成功"；
     *                - 如果状态码为2，返回"失败"；
     *                - 如果状态码为3，返回"跳过"；
     *                - 其他状态码，返回null。
     */
    public String convertTestStatus(int status){
        String statusInfo = null;
        switch (status) {
            case 1:
                statusInfo = "成功";
                break;
            case 2:
                statusInfo = "失败";
                break;
            case 3:
                statusInfo = "跳过";
                break;
            default:
                break;
        }
        return statusInfo;
    }
}